When to Use Which Widget
Choosing the right widget for displaying collections is crucial for performance and user experience.
Key point: Use builder constructors (ListView.builder, GridView.builder, SliverList/SliverGrid with delegates) for large or infinite lists to build items lazily.
ListView Patterns
ListView provides several constructors for different use cases.
ListView Constructors
ListView(children: [...])— small, fixed-size lists.ListView.builder(itemCount: n, itemBuilder: ...)— efficient for large lists.ListView.separated— insert separators between items easily.
Example Patterns (Conceptual)
Simple builder:
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(title: Text(item.title));
},
)
Separated:
ListView.separated(
itemCount: items.length,
itemBuilder: ...,
separatorBuilder: (context, index) => Divider(),
)
Key Points
- Use const where possible for static children.
- Provide keys (ValueKey or ObjectKey) for stateful list items that may reorder to preserve state.
GridView and Responsive Grids
GridView provides flexible layouts for displaying items in a grid format.
GridView Options
GridView.count— quick fixed column grids.GridView.builderwithSliverGridDelegateWithFixedCrossAxisCountorWithMaxCrossAxisExtent— flexible, performant grids.
Responsive Grid Example
Use maxCrossAxisExtent to let items adapt to available width:
GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 3/4,
),
itemBuilder: ...
)
Keys and Preserving State
Keys are essential for maintaining state when lists change.
Using Keys
- Use keys to preserve identity when reordering or updating lists.
ValueKey(item.id)is common when items have stable IDs.- Without keys, Flutter may rebuild and lose state (e.g., text field cursor position) when items move.
Example
ListTile(key: ValueKey(item.id), title: Text(item.name));
Heterogeneous Lists and Item Types
Lists often contain different types of items that require different widgets.
Handling Different Item Types
- When lists contain different item widgets (headers, cards, ads), switch on item type in itemBuilder.
- For Sliver-based lists, use SliverList with child delegates that produce different widgets.
Pattern
if (item is Header) return HeaderWidget(...);
if (item is Product) return ProductTile(...);
Swipe-to-Dismiss and Dismissible
Dismissible widgets enable intuitive deletion gestures.
Using Dismissible
Use Dismissible to allow swipe-to-delete with animation and background widgets. Provide a unique key and handle confirm/undo flows.
Example
Dismissible(
key: ValueKey(item.id),
background: Container(color: Colors.red, child: Icon(Icons.delete)),
onDismissed: (direction) { cart.remove(item); },
child: ListTile(title: Text(item.name)),
)
UX note: Support undo via SnackBar with action that restores item; avoid immediate permanent deletion without confirmation in critical flows.
ReorderableListView
ReorderableListView enables drag-and-drop reordering of list items.
Using ReorderableListView
Use ReorderableListView for drag-and-drop reordering. Maintain underlying list order and update state on onReorder(oldIndex, newIndex).
Important: Provide keys on children and ensure stable ids. Update models and persist new order if necessary.
Infinite Lists, Pagination, and Pull-to-Refresh
Pagination and refresh patterns are essential for handling large datasets.
Pagination Strategies
- Offset/limit (load next page at end): Common and simple.
- Cursor-based pagination: Robust for changing datasets.
Implementation
- Detect scroll position with
ScrollControllerand trigger loading when approaching the end. - Use
RefreshIndicatorto enable pull-to-refresh for manual reload.
Example Scroll Detection Pattern
_scrollController.addListener(() {
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) {
// load next page
}
});
Best Practices
- Debounce or guard concurrent page loads with a
isLoadingMoreflag. - Show loading footer or skeleton while loading.
- Handle empty results and end-of-list states to avoid repeated calls.
Slivers for Advanced Scrolling
Slivers provide powerful scrolling capabilities for complex UIs.
CustomScrollView with Slivers
CustomScrollView with SliverAppBar, SliverList, SliverGrid, SliverToBoxAdapter lets you mix different scrollable sections and create collapsing headers, sticky sections, and performant large lists. Use SliverChildBuilderDelegate for lazy child creation and provide keys as needed.
Use Case
Collapsing toolbar (SliverAppBar) + product grid below, with performant scrolling and pinned headers.
Performance and Memory Tips
Optimizing list performance is crucial for smooth user experience.
Performance Best Practices
- Use builder variants to avoid building off-screen widgets.
- For heavy list items, precompute or cache expensive subparts outside build (e.g., image decoding).
- Use const and small leaf widgets to minimize rebuild cost.
- Recycle image widgets with
cached_network_imageor similar to avoid re-downloading.
Binding Collections to State
Connecting lists to state management ensures reactive updates.
State Binding
- Keep source-of-truth list in a provider/state notifier. UI subscribes and rebuilds only necessary parts (use selectors to avoid full-list rebuilds if only a badge count changes).
- When modifying lists (add/remove/reorder), update the model then call notifyListeners or setState accordingly.
Example (Provider)
final items = context.watch().items;
ListView.builder(... itemCount: items.length ...)
Optimization
Use ListView.builder and item-level ChangeNotifier or ValueListenable for per-item state to avoid rebuilding the entire list on small changes.
Empty, Loading, and Error States
Providing feedback for different states improves user experience.
User Feedback States
- Loading: Centered spinner or skeleton placeholders.
- Empty: Friendly illustration and CTA.
- Error: Retry action and clear error copy.
For paginated lists, show loading footer and preserve previously loaded items while loading more.
Accessibility and Touch Targets
Making lists accessible ensures all users can interact with them effectively.
Accessibility Best Practices
- Ensure tappable list items meet minimum size and have proper semantics for screen readers.
- Provide meaningful labels for each item and use
ListTilesemantics where appropriate.
Exercises
Practice what you've learned with these exercises:
1. Product list with grid and pagination
Build a grid of product cards that loads initial page, loads next page on scroll, and shows a loading footer. Include a pull-to-refresh to reload the first page.
2. Reorderable todo list with persistence
Create a ReorderableListView for todos. Persist order locally (SharedPreferences or local JSON file) and reload on app start.
3. Heterogeneous feed with Slivers
Build a CustomScrollView containing: a SliverAppBar, a SliverList of posts, a SliverToBoxAdapter ad banner, and a SliverGrid of recommendations.
4. Swipe-to-delete with undo
Implement a dismissible shopping list where swiping removes item and shows SnackBar with Undo action to restore it.
Session Assignment
Complete Exercises 1 and 4. Provide code, screenshots for loading/empty/error states, and a short README describing your pagination approach and how you avoided duplicate loads.